package sim1011.poltergeist;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;

import sim1011.components.Component;
import sim1011.starter.EctoMobileStarter;
import sim1011.starter.Logger;
import sim1011.status.Status;

public class Arbiter implements java.io.Serializable {
	private static final long serialVersionUID = -2832738764527613829L;
	private int statusRequestCounter;
	
	public static float range(float max, float min, float value) {
		return ((value - min) / (max - min)) * EctoMobileStarter.MAX_RANK_VAL;
	}
	
	private ArrayList<Arbiter> arbiters = new ArrayList<Arbiter>();
	protected float cost;
	// rank che considera la combinazione lineare di tutti i parametri
	protected HashMap<String, Float> finalRank = new HashMap<String, Float>();
	protected Status status;
	
	public Arbiter() {	
	}

	public Arbiter(Status status) {
		this.status = status;
		this.statusRequestCounter = 0;
		if(EctoMobileStarter.DUMMY_P != 0){
			arbiters.add(new DummyArbiter(finalRank));
		}else {
			if (EctoMobileStarter.COST_P != 0.0f)
				arbiters.add(new CostArbiter(status,
						EctoMobileStarter.COST_P));
			if (EctoMobileStarter.BATTERY_P != 0.0f)
				arbiters.add(new BatteryArbiter(status,
						EctoMobileStarter.BATTERY_P));
			if (EctoMobileStarter.TRAFFIC_P != 0.0f)
				arbiters.add(new TrafficArbiter(status,
						EctoMobileStarter.TRAFFIC_P));
			if (EctoMobileStarter.TOTAL_EXEC_P != 0.0f)
				arbiters.add(new TotalExecArbiter(status,
						EctoMobileStarter.TOTAL_EXEC_P));
			if (EctoMobileStarter.EXEC_MOBILE_P != 0.0f)
				arbiters.add(new ExecMobileArbiter(status,
						EctoMobileStarter.EXEC_MOBILE_P));
			if (EctoMobileStarter.DO_NOTHING_P != 0.0f)
				arbiters.add(new DoNothingArbiter(status,
						EctoMobileStarter.DO_NOTHING_P));
		}
		
		
		
	}

	/**
	 * Metodo che si occupa di prendere la decisione di migrazione di un
	 * componente. TODO: per ora metodo provvisorio che con proabilità 1/2 forza
	 * la migrazione.
	 * 
	 * @param component
	 *            Il componente su cui si deve prendere una decisione
	 * @return nodeID su cui migrare
	 */
	public String decide(String containerID, String componentName) {
		int rand = EctoMobileStarter.r.nextInt(100);
		if (rand % 2 == 0) {
			return containerID;
		} else {
			int rand2 = EctoMobileStarter.r.nextInt(100);
			if (rand < 70) {
				int rand3 = EctoMobileStarter.r
						.nextInt(EctoMobileStarter.TOTAL_NODES - 1);
				String nextContainerID = "Node." + rand3;
				while (nextContainerID.equals(containerID)) {
					rand3 = EctoMobileStarter.r
							.nextInt(EctoMobileStarter.TOTAL_NODES - 1);
					nextContainerID = "Node." + rand3;
				}
				return nextContainerID;
			} else {
				return "Node.M";
			}
		}
	}

	/**
	 * Metodo che si occupa della decisione di migrazione nel caso in cui la
	 * batteria del nodo mobile sia scarica, quindi non li prende in
	 * considerazione
	 * 
	 * @param component
	 * @return
	 */
	public String decideOnLowBattery(String containerID, String componentName) {
		int rand = EctoMobileStarter.r
				.nextInt(EctoMobileStarter.TOTAL_NODES - 1);
		String nextContainerID = "Node." + rand;
		return nextContainerID;
	}

	private String getBestNode() {
		Iterator it = finalRank.keySet().iterator();
		String best = "";
		float bestCost = Float.MIN_VALUE;
		while (it.hasNext()) {
			String cur = (String) it.next();
			float curCost = finalRank.get(cur).floatValue();
			if (bestCost < curCost) {
				best = cur;
				bestCost = curCost;
			}
		}
		return best;
	}
	
	public void incrementCost(float increment) {
		this.cost += increment;
	}

	public String makeDecision(String component, String curContainer) {
		finalRank = new HashMap<String, Float>();
		ArrayList<String> availableNodes = status.getAvailableNodesName(status
				.getComponentsRequiredCPU().get(component), status
				.getComponentsRequiredMemory().get(component));
		for (String av : availableNodes)
			finalRank.put(av, new Float(EctoMobileStarter.MIN_RANK_VAL));
		// modifica dei pesi solo se DYNAMIC_P � attiva
		if(EctoMobileStarter.DYNAMIC_P > 0){
			if(this.statusRequestCounter < status.getStatusRequestCounter()){
				//aggiorna i pesi se necessario
				if(status.getTrafficIncrementPerc() > EctoMobileStarter.TRAFFIC_INCREMENT_THRESHOLD_PERC){
					System.out.print("TRAFFICO "+EctoMobileStarter.TRAFFIC_P+" ");
					EctoMobileStarter.TRAFFIC_P += EctoMobileStarter.TRAFFIC_INCREMENT;
					System.out.print("incrementato a "+EctoMobileStarter.TRAFFIC_P+"\n");
				}
				if(status.getCostIncrementPerc() > EctoMobileStarter.COST_INCREMENT_THRESHOLD_PERC){
					System.out.print("COSTO "+EctoMobileStarter.COST_P+" ");
					EctoMobileStarter.COST_P += EctoMobileStarter.COST_INCREMENT;
					System.out.print("incrementato a "+EctoMobileStarter.COST_P+"\n");
				}				
				// imposta il contatore dell'arbitro come quello dello stato
				this.statusRequestCounter = status.getStatusRequestCounter();
			}
		}
		for (Arbiter arb : arbiters)
			arb.makeYourDecision(component, curContainer, availableNodes, finalRank);
		String bestNode = getBestNode();		
		/*stampa dello stato dei nodi nel formato
		 * NOME_NODE		ranking
		 * 		nominal cpu
		 * 		nominal memory
		 * 		free cpu
		 * 		free memory
		 * 		QoS<NODO_SU_CUI_SI_TROVA_COMPONENTE, NODO_CORRENTE(quello su cui dovrebbe andare)>
		 * 		migration counter
		 * 		battery consumption
		 */
		 
		//Logger.logPolicy("==================== START POLICY APPLICATION ====================");
		//StringBuffer toPrint = new StringBuffer("COMPONENT: "+component+" ON NODE "+curContainer+"\n");
		//toPrint.append("\t"+component+" required CPU: "+status.getComponentsRequiredCPU().get(component)+"\n");
		//toPrint.append("\t"+component+" required MEM: "+status.getComponentsRequiredMemory().get(component)+"\n\n");
		
		//toPrint.append("SELECTED NODE: "+bestNode+"\nAVAILABLE NODE LIST: " + availableNodes.size() + "\n");		
		for (String node : availableNodes){					
			//toPrint.append("\n\t"+node+"\t\t"+finalRank.get(node)+"\n");
			//toPrint.append("\tNOMINAL CPU: "+status.getResourceManagersNominalCPU().get(node)+"\n");
			//toPrint.append("\tNOMINAL MEMORY: "+status.getResourceManagersNominalMemory().get(node)+"\n");
			//toPrint.append("\tAVERAGE FREE CPU: "+status.getAverageFreeCpu(node)+"\n");
			//toPrint.append("\tAVERAGE FREE MEMORY "+status.getAverageFreeMemory(node)+"\n");

			if(node.equals(curContainer)){
				//toPrint.append("\tQoS<"+curContainer+","+node+">: STESSO NODO"+"\n\n");
			}else{
				//toPrint.append("\tQoS<"+curContainer+","+node+">: "+status.getLinkQoS(curContainer, node)+"\n");				
			}
			if(curContainer.equals("Node.M")) {
				if(node.equals("Node.M")){
				//toPrint.append("BATTERY CONSUMPTION FOR EXECUTION: "+
				//		(status.getComponentsRequiredCPU().get(component)*EctoMobileStarter.BATTERY_FOR_OPERATION_CPU+
				//		status.getComponentsRequiredMemory().get(component)*EctoMobileStarter.BATTERY_FOR_MEMORY_REQ)+"\n");
				}
				else{
					float newQoS = 1 + ((status.getLinkQoS(curContainer, node)-1)/11);
					float consume = ((EctoMobileStarter.BATTERY_FOR_TRANSFER/(newQoS))*(Component.size(status.getComponentsRequiredCPU().get(component).intValue(), status.getComponentsRequiredMemory().get(component).intValue()*2) + EctoMobileStarter.MIGRATION_OVERHEAD));						
					//toPrint.append("BATTERY CONSUMPTION FOR MIGRATION: "+consume+"\n");
				}		
			}	
		}
		//Logger.logPolicy(toPrint.toString());
		//Logger.logPolicy("==================== END POLICY APPLICATION ====================");
		
		return bestNode;
	}

	public void makeYourDecision(String component, String curContainer,
			ArrayList<String> availableNodes, HashMap<String, Float> finalRank) {
		// implement in subarbiter
	}
}